package dviz.visualAspect;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.HashMap;

import dviz.visualAspect.MetaPropertyMap.InterpolateType;

import edu.utdallas.zxq0710000.vizInstruct.*;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.Translator;
import javassist.bytecode.AccessFlag;

public class XMLClassLoader extends VisualizedClassLoader {
	URL XMLInstructFile;
	ProgramInstructDocument instructDocument;
	HashMap<String, ObjectMap> objectMaps;
	HashMap<String, HashMap<String, FunctionMap>> functionMaps;

	// PropertyMaps<ToName,PropertyMap>
	HashMap<String, HashMap<String, PropertyMap>> propertyMaps;

	public XMLClassLoader(FunctionInvokeHandler functionInvokeHandler,
			URL XMLInstructFile) throws Exception {
		super(functionInvokeHandler);
		this.XMLInstructFile = XMLInstructFile;

		instructDocument = ProgramInstructDocument.Factory
				.parse(XMLInstructFile);

		initializeInstruct(instructDocument);
	}

	public void initializeInstruct(ProgramInstructDocument instructDocument) {
		objectMaps = new HashMap<String, ObjectMap>();
		functionMaps = new HashMap<String, HashMap<String, FunctionMap>>();
		propertyMaps = new HashMap<String, HashMap<String, PropertyMap>>();

		ObjectMap[] objectMap = instructDocument.getProgramInstruct()
				.getObjectMapArray();
		FunctionMap[] functionMap = instructDocument.getProgramInstruct()
				.getFunctionMapArray();
		PropertyMap[] propertyMap = instructDocument.getProgramInstruct()
				.getPropertyMapArray();
		for (int i = 0; i < objectMap.length; i++) {
			String classname = objectMap[i].getTargetClass().getName();
			objectMaps.put(classname, objectMap[i]);
			functionMaps.put(classname, new HashMap<String, FunctionMap>());
			propertyMaps.put(classname, new HashMap<String, PropertyMap>());
		}
		for (int i = 0; i < functionMap.length; i++) {
			String classname = functionMap[i].getClassName();
			String functionName = (functionMap[i].getFunctionName() + functionMap[i]
					.getSignature()).replace(" ", "");
			functionMaps.get(classname).put(functionName, functionMap[i]);
		}

		for (int i = 0; i < propertyMap.length; i++) {
			String classname = propertyMap[i].getClassName();
			String propertyname = propertyMap[i].getToName();
			propertyMaps.get(classname).put(propertyname, propertyMap[i]);
		}
	}

	private static String simplifySignature(CtBehavior method) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(method.getName());
		buffer.append("(");
		try {
			CtClass[] ptc = method.getParameterTypes();
			for (int i = 0; i < ptc.length; i++) {
				if (i != 0)
					buffer.append(",");
				buffer.append(ptc[i].getSimpleName());
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		buffer.append(")");
		return buffer.toString();
	}

	@Override
	public Translator getTranslator() {
		return new Translator() {

			@Override
			public void onLoad(ClassPool pool, String classname)
					throws NotFoundException, CannotCompileException {
				try {
					// Clazz has been instructed within XML file
					CtClass clazz = pool.get(classname);
					ObjectMap map = objectMaps.get(clazz.getName());

					if (map == null)
						return;
					System.out
							.println("XMLClassLoader->onLoad() Decorating Class->"
									+ classname);
					{
						// Putting callback for Finalizing to monitor the GC of
						// this object
						// TODO: Add finalize callback function
					}
					// Let this class inherit index interface
					{
						CtClass indexObj = classPool.get(INDEXOBJECT_INTERFACE);
						clazz.addInterface(indexObj);
					}

					// Add function handler instance as STATIC field
					CtField handlerField = new CtField(pool.get(HANDLER_CLASS),
							"__HANDLER_FIELD", clazz);
					{

						CtField f = new CtField(CtClass.intType, "__INDEX",
								clazz);
						clazz.addField(f);

						CtMethod getIndex = CtNewMethod.make(
								"public int __getIndex() {return __INDEX; }",
								clazz);
						clazz.addMethod(getIndex);

						CtMethod setIndex = CtNewMethod
								.make(
										"public void __setIndex(int indx) {__INDEX=indx;}",
										clazz);
						clazz.addMethod(setIndex);

					}
					handlerField.setModifiers(AccessFlag.STATIC);

					clazz
							.addField(
									handlerField,
									CtField.Initializer
											.byExpr("dviz.visualAspect.VisualizedClassLoader.getHandlerByLoader(($w)"
													+ UID + ")"));
					// Add call back to the invocation handler
					{
						CtBehavior methods[] = clazz.getDeclaredBehaviors();

						for (CtBehavior ctm : methods) {
							if (ctm.getModifiers() == Modifier.STATIC)
								continue;
							boolean insertBefore = true;
							Object behaviorMap = null;
							if (ctm instanceof CtMethod) {
								boolean decorateMethod = false;
								String signature = simplifySignature(ctm);
								// TODO: Modify the function map naming rule.
								// Also include fuzzy match func(int,int,int) to
								// func()
								FunctionMap functionMap = functionMaps.get(
										clazz.getName()).get(signature);
								if (signature.startsWith("remove"))
									System.currentTimeMillis();

								if (functionMap == null) {
									// Second try, fuzzy match
									String candidate = null;
									for (String func : functionMaps.get(
											clazz.getName()).keySet()) {
										if (func
												.substring(0, func.length() - 2)
												.equals(
														signature
																.substring(
																		0,
																		signature
																				.indexOf("(")))) {
											if (candidate == null) {
												candidate = func;
											} else {
												candidate = null;
												break;
											}
										}
									}
									functionMap = functionMaps.get(
											clazz.getName()).get(candidate);
								}
								if (functionMap == null)
									continue;
								if (functionMap.getOrder() == FunctionMap.Order.AFTER)
									insertBefore = false;
								else
									insertBefore = true;
								behaviorMap = functionMap;
							} else {
								boolean decorateConstructor = false;
								// Check the instruct XML document, evaulate the
								// constructor signature and try to find a match
								String signature = ctm.getSignature();

								ConstructMap cona[] = map.getTargetClass()
										.getConstructorArray();
								if (cona == null || cona.length == 0)
									decorateConstructor = true;
								else
									for (ConstructMap s : cona) {
										String cs = s.getSignature().replace(
												" ", "");
										if (cs.equals(signature)) {
											behaviorMap = s;
											decorateConstructor = true;
											break;
										}
									}
								if (!decorateConstructor)
									continue;

							}
							System.out.printf("\tDecorating Method->%s\n", ctm
									.getName());
							String signature = ctm.getLongName();

							StringWriter codeString = new StringWriter();
							PrintWriter interceptCode = new PrintWriter(
									codeString);

							interceptCode.printf("{\n");
							interceptCode.printf(
									"%s __INVOKATION = new %s();\n",
									INVOKATION_CLASS, INVOKATION_CLASS);
							interceptCode.printf(
									"__INVOKATION.setClassName(\"%s\");\n",
									clazz.getName());
							interceptCode.printf(
									"__INVOKATION.setVisualObject(\"%s\");\n",
									map.getVisualObject().getName());
							if (ctm instanceof CtMethod) {
								FunctionMap functionMap = (FunctionMap) behaviorMap;
								interceptCode
										.printf(
												"__INVOKATION.setActionName(\"%s\");\n",
												functionMap.getVisualAction()
														.getName());

								interceptCode.printf(
										"__INVOKATION.setType(%d);\n",
										Invokation.TYPE_METHOD);
								interceptCode
										.printf("__INVOKATION.parameters = new java.util.HashMap();\n");
								ParamMap[] paramMaps = functionMap
										.getVisualAction().getParamMapArray();
								// Get the renaming of parameter and put it into
								// HashMap<String,Integer>
								HashMap<String, Integer> renaming = new HashMap<String, Integer>();
								{
									RenameType rename[] = functionMap
											.getRenameArray();
									for (RenameType rn : rename) {
										renaming.put(rn.getName(), rn
												.getIndex());
									}
								}

								// For each param map insert the parameter into
								// the __INVOKATION instance.
								for (ParamMap param : paramMaps) {
									if (param.getFrom() != null) {
										interceptCode
												.printf(
														"__INVOKATION.parameters.put(\"%s\",($w)(%s));\n",
														param.getTo(),
														replaceNick(param
																.getFrom(),
																renaming));
									} else {
										CodeType codeType = param.getCode();
										String outVar = codeType.getOutVar();
										interceptCode
												.printf(
														"{\n%s\n __INVOKATION.parameters.put(\"%s\",($w)(%s));\n}\n",
														replaceNick(
																codeType
																		.getStringValue(),
																renaming),
														param.getTo(), outVar);
									}
								}
								// interceptCode
								// .printf("__INVOKATION.setParameters(__PARAMS);\n");
							} else {
								ConstructMap constructMap = (ConstructMap) behaviorMap;

								interceptCode
										.printf(
												"__INVOKATION.setConstructType(\"%s\");\n",
												map.getVisualObject().getType());
								interceptCode.printf(
										"__INVOKATION.setType(%d);\n",
										Invokation.TYPE_CONSTRUCTOR);
								interceptCode
										.printf("__INVOKATION.parameters = new java.util.HashMap();\n");
								{
									// Put visual object default initlialize
									// value
									// into parammap

									Parameter[] parameters = map
											.getVisualObject()
											.getParameterArray();
									if (parameters != null)
										for (Parameter parameter : parameters) {
											interceptCode
													.printf(
															"__INVOKATION.parameters.put(\"%s\",\"%s\");\n",
															parameter.getKey(),
															parameter
																	.getValue());
										}
								}
								if (constructMap != null) {
									ParamMap[] paramMaps = constructMap
											.getParamMapArray();
									// Get the renaming of parameter and put it
									// into
									// HashMap<String,Integer>
									HashMap<String, Integer> renaming = new HashMap<String, Integer>();
									{
										RenameType rename[] = constructMap
												.getRenameArray();
										for (RenameType rn : rename) {
											renaming.put(rn.getName(), rn
													.getIndex());
										}
									}

									// For each param map insert the parameter
									// into
									// the __INVOKATION instance.
									for (ParamMap param : paramMaps) {
										if (param.getFrom() != null) {
											interceptCode
													.printf(
															"__INVOKATION.parameters.put(\"%s\",($w)%d);\n",
															param.getTo(),
															replaceNick(param
																	.getFrom(),
																	renaming));
										} else {
											CodeType codeType = param.getCode();
											String outVar = codeType
													.getOutVar();
											interceptCode
													.printf(
															"{\n%s\n __INVOKATION.parameters.put(\"%s\",($w)(%s));\n}\n",
															replaceNick(
																	codeType
																			.getStringValue(),
																	renaming),
															param.getTo(),
															outVar);
										}
									}
								}
							}
							interceptCode
									.printf("__INVOKATION.setContext($0);\n");
							interceptCode
									.printf("__HANDLER_FIELD.handleFunctionCall(__INVOKATION);\n");
							interceptCode.printf("}\n");
							String codeStr = codeString.getBuffer().toString();

							System.out.println(codeStr);
							// if (ctm instanceof CtConstructor) {
							if (ctm instanceof CtConstructor)
								((CtConstructor) ctm).insertBeforeBody(codeStr);
							else {
								if (insertBefore)
									ctm.insertBefore(codeStr);
								else
									ctm.insertAfter(codeStr);
							}

							// } else {
							// ctm.insertBefore(codeStr);
							// }
						}
					}
					// End of call back interception code

					// Fetch Property information within class

					// Update 2009-1-14, propertyMap now supports using
					// Field,Getter and Code instead just the field name for
					// property data in VO

					// Update 2009-1-17, We can now specify Value Interpolation
					// inside property map. Those interpolation tag should be
					// processed correspondingly by AnimationSystem
					{
						// PropertyMap< AnimationObjectPropertyName, FieldName>
						HashMap<String, MetaPropertyMap> propertyMap = new HashMap<String, MetaPropertyMap>();
						for (PropertyMap p : propertyMaps.get(clazz.getName())
								.values()) {
							MetaPropertyMap mp = new MetaPropertyMap();
							// 2009-1-17, handling the interpolation section of
							// XML
							{
								String type = p.getInterpolate().toString();
								mp.interpolateType = InterpolateType
										.valueOf(type);

								Parameter parameter[] = p
										.getInterpolateParamArray();
								if (parameter != null) {
									HashMap<String, String> paramMap = new HashMap<String, String>();
									for (Parameter par : parameter) {
										paramMap.put(par.getKey(), par
												.getValue());
									}
									mp.interpolateParameter = paramMap;
								}
							}

							String dummyName = null;
							{
								// Insert generated function for mapped property
								StringWriter src = new StringWriter();
								PrintWriter dummyWriter = new PrintWriter(src);

								CodeType code = p.getCode();
								GetterType getter = p.getGetter();
								FieldType field = p.getField();
								try {
									if (code != null) {
										// If Property Map is using <Code> to
										// declare this propertyMap

										dummyName = "__"
												+ p.getToName()
												+ "_"
												+ Integer
														.toHexString(p
																.getToName()
																.hashCode());
										dummyWriter.printf(
												"public Object %s(){\n",
												dummyName);
										String outVar = code.getOutVar();
										String codeSrc = code.getStringValue();
										dummyWriter.printf("%s", codeSrc);
										dummyWriter.printf("return ($w)(%s);",
												outVar);
										dummyWriter.printf("}\n", dummyName);
										CtMethod dummyMethod = CtNewMethod
												.make(src.toString(), clazz);
										clazz.addMethod(dummyMethod);
									} else if (getter != null) {
										dummyName = getter.getName();
									} else if (field != null) {
										dummyName = "__"
												+ p.getToName()
												+ "_"
												+ Integer
														.toHexString(p
																.getToName()
																.hashCode());
										dummyWriter.printf(
												"public Object %s(){\n",
												dummyName);
										String fieldName = field.getName();
										dummyWriter.printf("return ($w)(%s);",
												fieldName);
										dummyWriter.printf("}\n", dummyName);
										CtMethod dummyMethod = CtNewMethod
												.make(src.toString(), clazz);
										clazz.addMethod(dummyMethod);
									} else
										throw new RuntimeException(
												"No property description XML section field,function or code");
								} catch (Exception e) {
									System.out.println("Error!\n"
											+ src.toString());
									throw e;
								}

							}
							mp.functionName = dummyName;
							if (dummyName == null)
								throw new RuntimeException();
							mp.type = MetaPropertyMap.Type.valueOf(p.getType()
									.toString());
							propertyMap.put(p.getToName(), mp);
						}
						clazzPropertyMap.put(clazz.getName(), propertyMap);
					}
					// End of Fetch Property Information
				} catch (Exception e) {
					System.err.println("Class load failure:" + classname);
					e.printStackTrace();
				}
			}

			@Override
			public void start(ClassPool pool) throws NotFoundException,
					CannotCompileException {
				
			}

			private String replaceNick(String in,
					HashMap<String, Integer> renaming) {
				for (String k : renaming.keySet()) {
					// replace occurance of $varName to $index
					in = in.replaceAll("\\$" + k + "(?=($|[^A-Za-z_$]+))",
							"\\$" + renaming.get(k));
				}
				return in;
			}

		};
	}
}
